package net.sf.midollo.io;

import java.io.IOException;
import java.io.InterruptedIOException;

public class IOStreamConnectorFullDuplex implements Runnable {

  protected final Object errorLock = new Object();
  protected IOException error;
  protected IOStreamConnector connectorRequest;
  protected IOStreamConnector connectorResponse;

  public IOStreamConnectorFullDuplex(IOStream[] ioStreams, int bufSizeRequest, int bufSizeResponse) throws IOException {
    this(ioStreams[0], ioStreams[1], bufSizeRequest, bufSizeResponse);
  }

  public IOStreamConnectorFullDuplex(IOStream ioRequest, IOStream ioResponse, int bufSizeRequest, int bufSizeResponse) throws IOException {
    this.connectorRequest = new IOStreamConnector(ioRequest, bufSizeRequest);
    this.connectorResponse = new IOStreamConnector(ioResponse, bufSizeResponse);
  }

  public IOException getError() {
    return this.error;
  }

  public void runConnector() throws IOException {
    // TODO optimize with only one extra thread, sync on request or response.

    Thread threadRequest = new Runner(this.connectorRequest);
    Thread threadResponse = new Runner(this.connectorResponse);
    synchronized (this.errorLock) {
      this.error = null;
      threadRequest.start();
      threadResponse.start();
      try {
        this.errorLock.wait();
      } catch (InterruptedException e) {
        if (this.error == null)
          this.error = new InterruptedIOException(e.toString());
      }
      if (this.error != null) {
        threadRequest.interrupt();
        threadResponse.interrupt();
        throw this.error;
      }
    }
    try {
      threadRequest.join();
      threadResponse.join();
    } catch (InterruptedException e) {
      if (this.error == null)
        this.error = new InterruptedIOException(e.toString());
    }
    if (this.error != null)
      throw this.error;
  }

  public void run() {
    try {
      this.error = null;
      runConnector();
    } catch (IOException e) {
      this.error = e;
    }
  }

  protected class Runner extends Thread {

    protected final IOStreamConnector connector;

    public Runner(IOStreamConnector connector) {
      super(connector);
      this.connector = connector;
    }

    public void run() {
      try {
        super.run();
      } finally {
        synchronized (IOStreamConnectorFullDuplex.this.errorLock) {
          IOException error = this.connector.getError();
          if (error != null && IOStreamConnectorFullDuplex.this.error == null)
            IOStreamConnectorFullDuplex.this.error = error;
          IOStreamConnectorFullDuplex.this.errorLock.notifyAll();
        }
      }
    }
  }
}
